//==============================================================================
//
//           DCalendar - the calendar class in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-01-31 06:44:29 $ $Revision: 1.14 $
//
//==============================================================================

#include "ofc/DCalendar.h"

#include <time.h>
#include <string.h>

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DTextable.h"
#include "ofc/DText.h"
#include "ofc/DDatable.h"
#include "ofc/DData.h"
#include "ofc/DDateTime.h"


#define DCL_SUNDAY      (DDT_SUNDAY)
#define DCL_MONDAY      (DDT_MONDAY)
#define DCL_TUESDAY     (DDT_TUESDAY)
#define DCL_WEDNESDAY   (DDT_WEDNESDAY)
#define DCL_THURSDAY    (DDT_THURSDAY)
#define DCL_FRIDAY      (DDT_FRIDAY)
#define DCL_SATURDAY    (DDT_SATURDAY)

#define DCL_ALL_MONTHS -1


// The calendar class implements a number of methods for generating calendars.

@interface DCalendar : Object <DTextable,DDatable>
{
@private
  int          _year;    // the year for the calendar
  int          _month;   // the month for the calendar (or DCL_ALL_MONTHS)
}

#endif


static int _firstWeekDay = DCL_SUNDAY;



@implementation DCalendar


//// Constructors

//
// Initialise to a default calendar (2003, all months)
//
// @return the object
//

- (DCalendar *) init
{
  [super init];

  _year  = 2003;
  _month = DCL_ALL_MONTHS;
  
  return self;
}

//
// Initialise to an all months calendar for a year
//
// @param year     the year for the calendar
//
// @return the object
//

- (DCalendar *) init :(int) year
{
  [self init];

  [self year :year];

  return self;
}

//
// Initialise to a month calendar
//
// @param year     the year for the calendar
// @param month    the month for the calendar
//
// @return the object
//

- (DCalendar *) init :(int) year :(int) month
{
  [self init];

  [self year  :year ];
  [self month :month];

  return self;
}


//// First week day methods

//
// Get the first week day (initialy DCL_SUNDAY)
//
// @return the first week day (DCL_SUNDAY or DCL_MONDAY)
//

+ (int) firstWeekDay
{
  return _firstWeekDay;
}

//
// Set the first week day
//
// @param day      the day number for the first week day (DCL_SUNDAY or DCL_MONDAY)
//

+ (void) firstWeekDay :(int) day
{
  if ((day >= DCL_SUNDAY) && (day <= DCL_SATURDAY))
  {
    _firstWeekDay = day;
  }
}


//// Year methods

//
// Get the year of the calendar
//
// @return the year
//

- (int) year
{
  return _year;
}

//
// Set the year for the calendar
//
// @param year      the year for the calendar
//
// @return the object
//

- (DCalendar *) year :(int) year
{
  _year = year;

  return self;
}

//
// Check if a year is a leap year
//
// @param year     the year to be checked
//
// @return is it ?
//

+ (BOOL) isLeapYear :(int) year
{
  return [DDateTime isLeapYear :year];
}

//
// Return the number of leap years in a year range
//
// @param from     the start year
// @param to       the last year (incl, to >= from)
//
// @return the number of leap years
//

+ (int) leapYears :(int) from :(int) to
{
  from--;
  
  return (to / 4 - from / 4) - (to / 100 - from / 100) + (to / 400 - from / 400);
}


//// Month methods

//
// Return the month of the calendar
//
// @return the month (or DCL_ALL_MONTHS)
//

- (int) month
{
  return _month;
}

//
// Set the month for the calendar
//
// @param month    the month for the calender (or DCL_ALL_MONTHS)
//
// @return the object
//

- (DCalendar *) month :(int) month
{
  if (((month >= 1) && (month <= 12)) || (month == DCL_ALL_MONTHS))
  {
    _month = month;
  }
  else
    WARNING(DW_ARG_OUT_RANGE, "month");

  return self;
}

//
// Return the number of days in a month
//
// @param year     the year of the month
// @param month    the month (1..12)
//
// @return the number of days in the month (1..31) or (-1)
//

+ (int) daysInMonth :(int) year :(int) month
{
  return [DDateTime daysInMonth :year :month];
}


//// Day methods

//
// Return the week day of a date
//
// @param year     the year  (1970..)
// @param month    the month (1..12)
// @param day      the day   (1..31)
//
// @return the week day (DCL_SUNDAY..DCL_SATURDAY)
//

+ (int) weekDay :(int) year :(int) month :(int) day
{
  struct tm  date;

  memset(&date, 0, sizeof(struct tm));

  date.tm_year  = year - 1900;
  date.tm_mon   = month - 1;
  date.tm_mday  = day;

  mktime(&date);

  return date.tm_wday;
}


//// Calendar methods

//
// Return the calendar in a data string
//
// @return a (new) data string with the calendar (7 elements per week, 0 = day outside month)
//

- (DData *) toData
{
  DData *cal = [[DData alloc] init];

  int month = (_month == DCL_ALL_MONTHS) ? 1  : _month;
  int last  = (_month == DCL_ALL_MONTHS) ? 12 : _month;

  while (month <= last)
  {
    int days = [DCalendar daysInMonth :_year :month];
    int day  = [DCalendar weekDay     :_year :month :1];
    int pad;
    int cnt;
     
    day = (_firstWeekDay - day + 6) % 7 - 5;
    cnt = 0;
    
    while (day < 1)
    {
      [cal push :0];
      day++;
      cnt++;
    }
    
    while (day <= days)
    {
      [cal push :(unsigned char) day];
      day++;
      cnt++;
    }

    pad = (cnt % 7 == 0) ? cnt : (cnt / 7 + 1) * 7;
    while (cnt < pad)
    {
      [cal push :0];
      cnt++;
    }

    month++;
  }

  return cal;
}

//
// Return the calendar in a text string
//
// @return a (new) text string object
//

- (DText *) toText
{
  DText *cal = [DText new];
  DText *hdr = nil;
   
  char  str[256];
                             
  int month = (_month == DCL_ALL_MONTHS) ? 1  : _month;
  int last  = (_month == DCL_ALL_MONTHS) ? 12 : _month;

  struct tm date;
    
  memset(&date, 0, sizeof(date));

  [cal format :"%4d\n", _year];
  
  while (month <= last)
  {
    int days = [DCalendar daysInMonth :_year :month];
    int day  = [DCalendar weekDay     :_year :month :1];
    int cnt;
    
    date.tm_year  = _year - 1900;
    date.tm_mon   = month - 1;
    date.tm_mday  = 1;

    mktime(&date); 
    strftime(str, sizeof(str), "%B\n", &date);
    [cal append :str];

    if (hdr == nil)
    {
      date.tm_mday = _firstWeekDay - day + 8;
      
      if (date.tm_mday > 7)
        date.tm_mday -= 7; 
      
      hdr = [[DText alloc] init];

      for (cnt = 0; cnt < 7; cnt++)
      {
        mktime(&date);
        strftime(str, sizeof(str), " %a", &date);
        [hdr append :str];
        
        date.tm_mday++;
      }
      [hdr push :'\n'];
    }
    [cal append :[hdr cstring]];
    
    day = (_firstWeekDay - day + 6) % 7 - 5;

    cnt = 1;
    while (day < 1)
    {
      [cal append :"    "];
      
      day++;
      cnt++;
    }

    while (day <= days)
    {
      snprintf(str, sizeof(str), "  %2d", day);
      
      [cal append :str];

      day++;
      if (++cnt > 7) 
      {
        [cal append :"\n"];
        cnt = 1;
      }
    }

    while (cnt <= 7)
    {
      [cal append :"    "];
      
      cnt++;
    }
    
    [cal append :"\n\n"];
    
    month++;
  }
  [hdr free];
  
  return cal;
}

@end

/*===========================================================================*/

